using System;
using System.Collections.Generic;
using System.Text;
using CreamX.Framework.Game.Components;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using CreamX.Framework.Game.GameEngine;
using CreamX.Framework.QuadTree;
using CreamX.Framework.SceneGraph;
using CreamX.Common;



namespace CreamX.Framework.Game
{
    /// <summary>
    /// Base object which all other scene objects must inherit.
    /// </summary>
    public class CreamXObject : CreamXBase, ICloneable, ISceneItem
    {
        #region Privates
        private List<Mount> _mountedObjects = new List<Mount>();
        internal CreamXMountPointComponent _mountComponent;
        private FRect _rect = new FRect();
        private List<QuadTreePositionItem<CreamXObject>> _list = new List<QuadTreePositionItem<CreamXObject>>();
        private List<QuadTreePositionItem<CreamXObject>> _objectsInLocalSpace;
        
        private bool changeQuadPos = false;
        private bool _flipX = false;
        private bool _flipY = false;
        private bool _ignoreCameraPos;
        private bool _template;
        private bool _visible = true;
        private bool _markForDelete;
        protected bool _isRegistered;

        private float _rotation;

        protected float _scale;
        private int _layer = 0;
        private Vector2 _position;
        private Vector2 _rotationorigin;
        private Color _color = Color.White;
        protected BoundingSphere _boundingSphere;
        protected BoundingBox _boundingBox;
        private Dictionary<string, object> _properties = new Dictionary<string, object>();
        private object _tag;
        private List<string> _objectType = new List<string>();
        internal QuadTreePositionItem<CreamXObject> _positionItem;
        #endregion

        #region Constructor
        public CreamXObject()
        {
            Components = new List<CreamXComponent>();
            ID = Guid.NewGuid();
            _boundingBox = new BoundingBox();
            _boundingSphere = new BoundingSphere();
        }
        #endregion

        #region Private Variables
        internal CreamXPhysicsComponent Physics
        {
            get { return GetComponent<CreamXPhysicsComponent>(); }
        }

        internal CreamXCollisionComponent Collision
        {
            get { return GetComponent<CreamXCollisionComponent>(); }
        }
        

        internal float Radius
        {
            get
            {
                if (Collision != null)
                    if (Collision.CollisionType == CollisionType.Circle)
                        return GetBoundingSphere().Radius;
                    else
                        return DisplayWidth / 2;
                else
                    return GetBoundingSphere().Radius;
            }
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Unique Identifier for an object in the scene data
        /// </summary>
        public Guid ID;

        /// <summary>
        /// Gets or sets the value of wether to flip the object's texture horizontally
        /// </summary>
        public bool FlipX
        {
            get { return _flipX; }
            set { _flipX = value; }
        }

        /// <summary>
        /// Gets or sets the value of wether to flip the object's texture vertically
        /// </summary>
        public bool FlipY
        {
            get { return _flipY; }
            set { _flipY = value; }
        }

        /// <summary>
        /// Gets or sets the list of mounted objects which are configured using the CreamXMountPoint component
        /// </summary>
        public List<Mount> MountedObjects
        {
            get { return _mountedObjects; }
            set { _mountedObjects = value; }
        }

        /// <summary>
        /// Gets or sets the Tag object of the scene object.
        /// </summary>
        /// <remarks>Tag is available for you to attach a custom object to a game object</remarks>
        public object Tag
        {
            get { return _tag; }
            set { _tag = value; }
        }

        /// <summary>
        /// Gets or sets the value of wether to ignore the camera position
        /// </summary>
        /// <remarks>Ignoring the camera position will always show the image at its set position</remarks>
        public bool IgnoreCameraPosition
        {
            get { return _ignoreCameraPos; }
            set { _ignoreCameraPos = value; }
        }

        /// <summary>
        /// Gets or sets the value of wether to delete the object from the scene after the current update
        /// </summary>
        public bool MarkForDelete
        {
            get { return _markForDelete; }
            set
            {
                _markForDelete = value;
                if (MountedObjects.Count > 0)
                {
                    foreach (Mount obj in MountedObjects)
                    {
                        obj.Object.MarkForDelete = true;
                    }
                }
            }
        }

        /// <summary>
        /// Gets the ObjectTypeFlags
        /// </summary>
        public uint ObjectTypeFlags;

        /// <summary>
        /// Gets the current scene the object is a member of
        /// </summary>
        public CreamXSceneData Scene
        {
            get { return CreamXGameEngine.Instance.SceneManager.CurrentScene; }
        }

        /// <summary>
        /// Gets or sets the visible state of the game object
        /// </summary>
        public bool Visible
        {
            get { return _visible; }
            set { _visible = value; }
        }

        /// <summary>
        /// Gets or sets the layer of the game object
        /// </summary>
        /// <remarks>Valid values are 0-10</remarks>
        public int Layer
        {
            get { return _layer; }
            set { _layer = value; }
        }

        /// <summary>
        /// Gets or sets the origin of the game object for use with drawing. 
        /// </summary>
        /// <remarks>A common way to use origina is to set it to half the width and height of the game object</remarks>
        public Vector2 Origin
        {
            get { return _rotationorigin; }
            set { _rotationorigin = value; }
        }

        /// <summary>
        /// Gets or sets the rotation value in radians
        /// </summary>
        public float Rotation
        {
            get { return _rotation; }
            set
            {
                _rotation = value;
                if (_rotation > MathHelper.TwoPi) _rotation -= MathHelper.TwoPi;
                if (_rotation < 0) _rotation += MathHelper.TwoPi;
            }
        }

        /// <summary>
        /// Gets or sets the scale where 1 is the default size of the sprite
        /// </summary>
        public float Scale
        {
            get { return _scale; }
            set
            {
                _scale = value;
                if (_positionItem != null)
                    _positionItem.Size = new Vector2(DisplayWidth, DisplayHeight);
                if (_isRegistered)
                {
                    _boundingSphere.Radius = (float)DisplayWidth / 2;
                    ResetBoundingBox();
                }

            }
        }

        /// <summary>
        /// Gets the position of the game object
        /// </summary>
        /// <remarks>Use SetPosition to set this value</remarks>
        public Vector2 Position
        {
            get { return _position; }
        }

        /// <summary>
        /// Returns the display width of the Game object taking into account its scale.
        /// </summary>
        public float DisplayWidth
        {
            get
            {
                if (this is CreamXAnimatedObject)
                    return ((CreamXAnimatedObject)this).DisplayWidth;

                if (this is CreamXTileGrid)
                    return ((CreamXTileGrid)this).DisplayWidth;

                if (Material!=null&& Material.Texture != null)
                    return Material.Texture.Width * Scale;
                else
                    return Scale * 1;

            }
        }

        /// <summary>
        /// Returns the display height of the Game object taking into account its scale.
        /// </summary>
        public float DisplayHeight
        {
            get
            {
                if (this is CreamXAnimatedObject)
                    return ((CreamXAnimatedObject)this).DisplayHeight;

                if (this is CreamXTileGrid)
                    return ((CreamXTileGrid)this).DisplayHeight;

                if (Material!=null && Material.Texture != null)
                    return Material.Texture.Height * Scale;
                else
                    return Scale * 1;
            }
        }

        /// <summary>
        /// Gets or sets the Template value. A game object template will not be rendered or updated in the main game loop
        /// </summary>
        public bool Template
        {
            get { return _template; }
            set { _template = value; }
        }

        /// <summary>
        /// Gets or sets the tint color of the game object
        /// </summary>
        public Color Color
        {
            get { return _color; }
            set { _color = value; }
        }

        /// <summary>
        /// Gets or sets the material of the game object
        /// </summary>
        public CreamXMaterialBase Material;

        /// <summary>
        /// Gets or sets the components collection
        /// </summary>
        public List<CreamXComponent> Components;

        /// <summary>
        /// Gets the value of wether the game object is registered in the scene
        /// </summary>
        public bool IsRegistered
        {
            get { return _isRegistered; }
            set { _isRegistered = value; }
        }

        #endregion

        #region Public Methods
        /// <summary>
        /// Adds a component to the objects component list, if the component is a bounds component or collision component it gets
        /// assigned to its corresponding instance
        /// </summary>
        /// <param name="component">The component to add</param>
        public CreamXComponent AddComponent(CreamXComponent component)
        {
            component.SceneObject = this;
            //if (component is CreamXBoundsComponent)
            //{
            //    BoundsComponent = (CreamXBoundsComponent)component;
            //}
            //if (component is CreamXCollisionComponent)
            //{
            //    CollisionComponent= (CreamXCollisionComponent)component;
            //}
            //if (component is CreamXPhysicsComponent)
            //{
            //    PhysicsComponent= (CreamXPhysicsComponent)component;
            //}
            if (this.IsRegistered)
                component.OnRegister();

            if (component is CreamXMountPointComponent)
                _mountComponent = (CreamXMountPointComponent)component;
            Components.Add(component);
            return component;
        }

        public void RemoveComponent(CreamXComponent component)
        {
            component.OnUnRegister();
            Components.Remove(component);
        }
        /// <summary>
        /// Returns a component of specified type. Use this to quickly get a component from the object
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T GetComponent<T>() where T : CreamXComponent
        {
            T local = default(T);
            foreach (CreamXComponent component in Components)
            {
                local = component as T;
                if (local != null)
                {
                    return local;
                }
            }
            return default(T);
        }

        public void SetObjectTypeFlag(uint flag)
        {
            if (!On(flag))
                ObjectTypeFlags |= flag;
        }

        public bool CheckType(uint type)
        {
            return On(type);
        }

        public bool CheckType(List<uint> type)
        {
            if (type == null)
                return false;
            foreach (uint __v in type)
            {
                if (CheckType(__v))
                    return true;
            }
            return false;
        }

        internal void ResetBoundingBox()
        {
            if (Material == null)
                return;
            Vector2 tl = Position - (Origin*Scale);
            Vector2 br= tl+new Vector2(DisplayWidth,DisplayHeight);
            _boundingBox = new BoundingBox(new Vector3(tl,0),
                new Vector3(br,0));
        }

        public void SetPosition(float x, float y)
        {
            if (_position.X != x || _position.Y != y)
            {
                changeQuadPos = true;
                Vector2 __v = new Vector2(x, y);
                SetPosition(__v);
            }
        }

        public void SetPosition(Vector2 position)
        {
            //if (float.IsNaN(position.X)|| float.IsNaN(position.Y ))
            //  throw new ArgumentException("NAN Not allowed");
            _position = position;
            _boundingSphere.Center = new Vector3(position, 0);
            //ResetBoundingBox();
            changeQuadPos = true;
            if (changeQuadPos)
            {
                if (_positionItem != null)
                    _positionItem.Position = _position;
                else
                    if(IsRegistered)
                        Scene.SetPositionItem(this);

            }
            
            ResetBoundingBox();
            
        }

        public void SetPosition(ref Vector2 position)
        {
            //if (float.IsNaN(position.X)|| float.IsNaN(position.Y ))
            //  throw new ArgumentException("NAN Not allowed");
            _position = position;
            _boundingSphere.Center = new Vector3(position, 0);

            ResetBoundingBox();
            if (_positionItem != null)
                _positionItem.Position = _position;
            else
                 Scene.SetPositionItem(this);
        }

        public void Move(float x, float y)
        {
            Vector2 __position = _position + new Vector2(x, y);
            SetPosition(__position);
        }

        public void Move(Vector2 amount)
        {
            Vector2 __position = _position + amount;
            SetPosition(ref __position);
        }

        public void Move(ref Vector2 amount)
        {
            Vector2 __position = _position + amount;
            SetPosition(ref __position);
        }

        public BoundingBox GetBoundingBox()
        {
            return _boundingBox;

        }

        public BoundingSphere GetBoundingSphere()
        {
            return _boundingSphere;

        }

        public virtual void OnRegister()
        {
            //Set bounding Sphere
            // _rotationorigin = new Vector2(Material.Texture.Width / 2, Material.Texture.Height / 2);
            _boundingSphere = new BoundingSphere();
            _boundingSphere.Center = new Vector3(Position, 0);
            if (Material != null)
            {
                _boundingSphere.Radius = (float)DisplayWidth / 2;
                ResetBoundingBox();
            }
            _isRegistered = true;
            foreach (CreamXComponent comp in Components)
            {
                comp.OnRegister();
                if (comp is CreamXMountPointComponent)
                {
                    foreach (Mount mnt in MountedObjects)
                    {
                        CreamXObject obj = Scene.CreateClone(mnt.Object.Name);
                        if (mnt.SnapToMount)
                            obj.SetPosition(Position + mnt.MountPoint.Position);
                        Scene.RegisterObject(obj);
                        mnt.Object = obj;
                        

                    }
                }
            }
            SetPosition(Position);
        
        }

        public List<QuadTreePositionItem<CreamXObject>> GetObjectsInLocalSpace(float LookWidth)
        {
            SetObjectsInLocalSpace(LookWidth);
            return _objectsInLocalSpace;
        }

        public QuadTreePositionItem<CreamXObject> QuadTreePositionItem
        {
            get { return _positionItem; }
        }

        public void AttachMountedObject(CreamXObject obj, string name)
        {
            if (_mountComponent == null)
            {
                _mountComponent = (CreamXMountPointComponent)AddComponent(new CreamXMountPointComponent());
            }
            //_mountComponent.AddMountPoint(name, Vector2.Zero, 0f);
            Mount mnt = new Mount();
            mnt.ObjectRef = obj.Name;
            mnt.Object = obj;
            mnt.MountPoint = _mountComponent.GetMountPoint(name);
            MountedObjects.Add(mnt);
        }

        public void CopyTo(CreamXObject __an)
        {
            __an.Material = Material;
            __an.Name = Name;
            __an.SetPosition(Position);
            __an.Scale = Scale;
            __an.Template = false;
            __an.Layer = Layer;
            __an.Origin = Origin;
            __an.Color = Color;
            __an.ObjectTypeFlags = ObjectTypeFlags;
            __an.Components.Clear();
            foreach (Mount mnt in MountedObjects)
            {
                __an.MountedObjects.Add((Mount)mnt.Clone());
            }
        }

        public override string ToString()
        {
            return Name;
        }
        #endregion

        #region ICloneable Members

        public virtual object Clone()
        {
            CreamXObject __o = new CreamXObject();
            CopyTo(__o);

            //__o.ObjectType.Clear();
            //__o.ObjectType.AddRange(ObjectType);
            return __o;
        }

        #endregion

        #region Private Methods
        public virtual void Draw(SpriteBatch spritebatch)
        {

            Vector2 position;

            if (IgnoreCameraPosition)
                position = Position;
            else
                position = Scene.Camera.GetRenderPosition(Position);
            if (FlipX)
            {
                //Scene.Camera.GetRenderScale(
                spritebatch.Draw(Material.Texture, position, null, _color, Rotation,
                    Origin, Scene.Camera.GetRenderScale(Scale), SpriteEffects.FlipHorizontally, Layer / 10);
            }
            if (FlipY)
            {
                spritebatch.Draw(Material.Texture, position, null, _color, Rotation,
                    Origin, Scene.Camera.GetRenderScale(Scale), SpriteEffects.FlipVertically, Layer / 10);
            }
            else
            {
                if (Material != null && Material.Texture != null)
                {
                    spritebatch.Draw(Material.Texture, position, null, _color, Rotation,
                       Origin, Scene.Camera.GetRenderScale(Scale) , SpriteEffects.None, (float)Layer / 10f);
                }

            }
        }

        internal virtual void Draw(SpriteBatch spritebatch, Vector2 offset, Color color)
        {
            Vector2 position;

            if (IgnoreCameraPosition)
                position = Position;
            else
                position = Scene.Camera.GetRenderPosition(Position);
            if (FlipX)
            {
                spritebatch.Draw(Material.Texture, position + offset, null, color, Rotation,
                    Origin, Scene.Camera.GetRenderScale(Scale), SpriteEffects.FlipHorizontally, Layer / 10);
            }
            if (FlipY)
            {
                spritebatch.Draw(Material.Texture, position + offset, null, color, Rotation,
                    Origin, Scene.Camera.GetRenderScale(Scale), SpriteEffects.FlipVertically, Layer / 10);
            }
            else
            {
                spritebatch.Draw(Material.Texture, position + offset, null, color, Rotation,
                    Origin, Scene.Camera.GetRenderScale(Scale), SpriteEffects.None, Layer / 10);
            }
        }

        internal virtual void Draw(SpriteBatch spritebatch, Vector2 position)
        {

            spritebatch.Draw(Material.Texture, position, null, _color, Rotation,
                Origin, Scale, SpriteEffects.None, Layer / 10);
        }

        public virtual void Update(float gameTime)
        {
            if (!MarkForDelete)
            {
                foreach (CreamXComponent __comp in Components)
                {
                    if (__comp.Enabled)
                    {
                        __comp.Update(gameTime);
                    }
                }
                if (_mountComponent != null)
                {
                    foreach (Mount mnt in MountedObjects)
                    {
                        mnt.Object.SetPosition(mnt.MountPoint.Position + Position);
                        if (mnt.InheritVisibility)
                            mnt.Object.Visible = Visible;
                    }
                }
            }
        }

        private bool On(uint bt)
        {
            return (ObjectTypeFlags & bt) == bt;
        }

        private void SetRotation(float p)
        {
            Rotation = p;
        }

        private float GetHeight()
        {
            return Material.Texture.Height * Scale;
        }

        private float GetWidth()
        {
            return Material.Texture.Width * Scale;
        }

        internal void SetObjectsInLocalSpace(float LookWidth)
        {
            CreamXProfiler.StartProfiling(ProfilerName.SetObjectsInLocalSpace);
            if (_objectsInLocalSpace == null)
                _objectsInLocalSpace = new List<QuadTreePositionItem<CreamXObject>>();
            else
                _objectsInLocalSpace.Clear();

         
            if (Scene.QuadTreeEnabled)
            {
                _rect.TopLeft = Position - new Vector2(LookWidth, LookWidth);
                _rect.BottomRight = Position + new Vector2(LookWidth, LookWidth);

                Scene.QuadTree.GetItems(
                    _rect, ref _objectsInLocalSpace);
            }
            
            CreamXProfiler.StopProfiling(ProfilerName.SetObjectsInLocalSpace);
        }

        internal CreamXObject ConvertToTileGrid()
        {
            CreamXTileGrid __grid = new CreamXTileGrid();
            CopyTo(__grid);
            __grid.Template = false;
            return __grid;
        }

        internal void OnSceneLoaded()
        {
            ProcessMounts();
        }

        private void ProcessMounts()
        {
            foreach (Mount mnt in MountedObjects)
            {
                if (mnt.Object == null)
                {
                    CreamXObject obj = Scene.CreateClone(mnt.ObjectRef);
                    Scene.RegisterObject(obj);
                    mnt.Object = obj;
                }
            }
        }

        #endregion

    }
}
